﻿using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using System.Windows.Media.Imaging;
using System.Windows.Media.Animation;

using Bling.Core;
using Bling.WPF;
using Bling.WPF.Util;
using Bling.Slides;
using Bling.Util;
using Bling.Shaders;
using Bling.Graphics;
namespace Bling.Example {
  using Bling.Matrices;
  using Bling.WPF3D;
  using Bling.Shaders.DX;
  /// <summary>
  /// Demonstrate a lerp between sphere and plane.
  /// </summary>
  public class Example2 : Canvas, ICanStart {
    private static readonly Uri AutumnUri = "Resources/Autumn.jpg".MakePackUri(typeof(Example2));
    // These three lines are boiler plate to start and stop the rendering loop as this canvas is active/inactive.
    private ICanStart Host;
    public void Start() { Host.Start(); }
    public void Stop() { Host.Stop(); }
    // the real work begins.
    public Example2() {
      // first, create some sliders to control the simulation.
      LabelSliderBl sliderX = new LabelSliderBl(this) {
        LabelName = "Lerp",
        ZIndex = 100,
      };
      LabelSliderBl sliderY = new LabelSliderBl(this) {
        LabelName = "Y",
        ZIndex = 100,
        LeftTop = sliderX.LeftBottom,
      };
      LabelSliderBl sliderZ = new LabelSliderBl(this) {
        LabelName = "Z",
        ZIndex = 100,
        LeftTop = sliderY.LeftBottom,
      };
      // create a new device in which to hold rendering information
      // this will actually cause rendering to occur in a WPF host. 
      var Host0 = new Bling.Shaders.DX.WPFHostBl(this, new PointBl(500, 500)) {
        LeftTop = 0d,
        ZIndex = 0,
      };

      // world matrix specifies where a render object will go in the world. Just set it to the default for now.
      MatrixBl<D4, D4> world = MatrixBl<D4, D4>.Identity;
      // so we can rotate the object in the world.
      world = sliderY.Value.PI2().RotateAround(new Point3DBl(0d, 1d, 0d)).Matrix;
      world = world * sliderZ.Value.PI2().RotateAround(new Point3DBl(0d, 0d, 1d));

      // represents where our eye is in the world.
      Point3DBl eye = new Point3DBl(0f, -2d, -4d);

      // Configure a perspective camera.
      PerspectiveCameraCl Camera = new PerspectiveCameraCl() {
        Position = eye,
        FieldOfView = .25.PI().AsDegrees,
      };

      // XXX: need more understanding on difference between view and project matrices
      //MatrixBl<D4, D4> view = eye.LookAt(0d, new Point3DBl(0, 1, 0));
      //MatrixBl<D4, D4> project = (.25.PI()).PerspectiveFov(500d / 500d, .1, 100d);
      // Light definitions borrowed from WPF 3D.


      // Define a surface where we interpolate between a plane and a sphere, controlled by the x slider.
      PSurface Plane = new PSurface(p => new Point3DBl(p, -1d));
      PSurface Sphere = GraphicDefinitions.Sphere;
      PSurface Surface = sliderX.Value.Lerp(Plane, Sphere);

      // Define how vetices are sampled, in this case, we create 200 by 200 vertices evenly sampled through surface.
      PSurfaceSample SurfaceSample = new PSurfaceSample(200, 200);
      // Create a texture from the Autumn resource Uri.
      Tex2D Autumn = Host0.Device.TextureFromUri(AutumnUri);
      // This code defines how Surface is rendered. It takes a function that provides
      // the vertiex index (n), surface's parametric coordinate (uv), and a vertex result
      // structure (vertex).
      Host0.Device.Render(SurfaceSample, (IntBl n, PointBl uv, IVertex vertex) => {
        // First, specify where the vertex will appear on the screen. The parametric coordinate (uv)
        // allows us to access the vertex's relative position, which we multiple by the world, view,
        // and project matrices.
        vertex.Position = (Surface * (world * Camera))[uv]; 
        // Compute normal for lighting. Parametric surface normals are computed
        // via symbolic differentiation. Only transform by the world matrix.
        // Must cast to point 3D because lighting computations doesn't work on 4D points,
        // casting will incorporate the w component into the resulting 3D point. 
        Point3DBl normal =        (Point3DBl)(Surface.Normals[uv] * world).Normalize;
        // Compute position for lighting. Again only transform by world matrix.
        Point3DBl usePosition = (Point3DBl)((Surface[uv] * (world)));
        // sample the Autumn texture using the current parametric point. The range of uv
        // is [-.5,-.5] to [+.5,+.5] while the range of a texture sample is [0,0] to [1,1].
        // As a result, we add a half a unit (0.5) to uv before sampling. 
        // Note that this computation will occur in the pixel shader where uv will be properly 
        // interpolated.
        ColorBl clr = Autumn[uv + 0.5];
        AmbientLightBl ambLight = new AmbientLightBl() { Color = Colors.White.Bl() };


        // Create lighting by addition that we want to use on the color.
        Lighting L = (new DiffuseMaterialCl() { Ambient = { Factor = 0.3 } } +
                      new SpecularMaterialCl() { Power = 2, Factor = 0.8 }).Apply(
                      new DirectionalLightBl() {
                        Direction = new Point3DBl(0, 0, -1),
                        Color = Colors.LightSeaGreen,
                      } + new SpotLightBl() {
                        Direction = new Point3DBl(1, -1, 1).Normalize,
                        Color = Colors.Red.Bl(),
                        Position = new Point3DBl(-2, 2, -2),
                        InnerConeAngle = 30.ToDegrees(),
                        OuterConeAngle = 120.ToDegrees(),
                      } + new AmbientLightBl() { Color = Colors.White });

        // Apply lighting, assign vertex color.
        vertex.Color = L[usePosition, normal, eye][Autumn, uv + 0.5];
      });
      // so start and stop work.
      Host = Host0.CurrentValue;
      // sliders go beneath the host.
      sliderX.LeftTop = Host0.LeftBottom;
    }
  }
}
